package openapi

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"gitee.com/bjf-fhe/apinx/errors"
	"github.com/getkin/kin-openapi/openapi3"
	"github.com/sirupsen/logrus"
)

type CallingSession struct {
	securityInputs       map[string]interface{}
	securitySchemes      openapi3.SecuritySchemes
	securityRequirements openapi3.SecurityRequirements
	//关于执行的具体配置
	mSel    *OperationSelection
	baseURL string
	path    string
	method  string
	op      *openapi3.Operation
	//以下是结果变量，用于标识执行结果
	extraInfos  *TestResult
	hasChange   bool
	resp        *http.Response
	handledMark map[string]interface{}
	//执行的临时输入变量
	inputs *CallConfig
}

func NewCalling() *CallingSession {
	return &CallingSession{
		extraInfos: &TestResult{},
		mSel:       &OperationSelection{},
	}
}

// SetSecurityByDef 从定义中获得安全信息，并完成设置，如果有详细自定义配置，可以调用SetSecurity，但建议使用SetSecurityByDef
// 注意，该函数应在SetPathAndMethod之后调用
func (c *CallingSession) SetSecurityByDef(inputs map[string]interface{}, t *openapi3.T) *CallingSession {
	if c.op == nil {
		panic("请在调用SetPathAndMethod之后调用函数SetSecurityByDef")
	}
	c.securityInputs = inputs
	if t.Components != nil {
		c.securitySchemes = t.Components.SecuritySchemes
	}
	if c.op.Security != nil {
		c.securityRequirements = *c.op.Security
	}
	if c.securityRequirements == nil {
		c.securityRequirements = t.Security
	}
	return c
}

func (c *CallingSession) SetSecurity(inputs map[string]interface{}, securityRequirements openapi3.SecurityRequirements, schemes openapi3.SecuritySchemes) *CallingSession {
	c.securityInputs = inputs
	c.securitySchemes = schemes
	c.securityRequirements = securityRequirements
	return c
}

func (c *CallingSession) SetSelection(op *OperationSelection) *CallingSession {
	c.mSel = op
	return c
}

func (c *CallingSession) SetServer(server *openapi3.Server, variables ...map[string]string) *CallingSession {
	if len(variables) > 0 {
		c.baseURL = GetBaseUrlFromServer(server, variables[0])
	} else {
		c.baseURL = GetBaseUrlFromServer(server, nil)
	}
	return c
}

func (c *CallingSession) SetPathAndMethod(path, method string, op *openapi3.Operation) *CallingSession {
	c.path = path
	c.op = op
	c.method = strings.ToLower(method)
	return c
}

func (c *CallingSession) parseUrl() (*url.URL, error) {
	return url.Parse(c.baseURL + c.path)
}

// 执行调用, args: 具体调用配置， fn 系统功能配置，两者区别：args是一次调用关于调用内容的具体配置，fn是调用方关于调用过程中的一些系统级别配置
func (c *CallingSession) generateBody(args *CallConfig, fn DefHandler) (requestBts []byte, err error) {

	//处理body
	ctType := args.RequestType
	if c.method == "post" || c.method == "put" || c.method == "patch" {
		//处理请求
		var requestMap map[string]interface{}
		var rawRequest interface{}
		var reqDef *openapi3.MediaType
		if c.mSel.RequestBody == nil {
			c.mSel.RequestBody = &RequestBodySelection{
				Default: args.RequestType,
			}
		}
		if c.op.RequestBody != nil && c.op.RequestBody.Value != nil && c.op.RequestBody.Value.Content != nil {
			reqDef = c.op.RequestBody.Value.Content.Get(c.mSel.RequestBody.Default)
		}

		if reqDef == nil {
			//c.mSel.RequestBody.Default是最终的cttype，不要使用cttype变量，部分情况下，cttype是空的
			if fn.CreateResponseIfNotDefined() == CreateDefWhenNotDefined {
				c.mSel.RequestBody.Default = ctType
				var raw = args.RequestBody
				var inputType string
				var schema *openapi3.Schema
				if ctType == "application/json" {
					var bts = raw.([]byte)
					if bts[0] == '{' {
						inputType = "object"
					} else if bts[0] == '[' {
						inputType = "array"
					} else if bts[0] == '"' {
						inputType = "string"
					} else if bts[0] == 't' || bts[0] == 'f' {
						inputType = "boolean"
					} else if bts[0] >= '0' && bts[0] <= '9' {
						inputType = "number"
					}

					if inputType == "object" {
						schema, requestMap = generatePropertiesForJsonObject(raw)
					} else {
						schema = &openapi3.Schema{
							Type: &openapi3.Types{inputType},
						}
					}
				} else if ctType == "application/x-www-form-urlencoded" || ctType == "multipart/form-data" {
					var values = raw.(url.Values)
					inputType = "object"
					schema, requestMap = generatePropertiesForValues(values)
				} else {
					logrus.Errorln("未知的请求类型", ctType)
					goto handle
				}
				reqDef = &openapi3.MediaType{
					Schema: &openapi3.SchemaRef{
						Value: schema,
					},
				}
				if c.op.RequestBody != nil && c.op.RequestBody.Value != nil {
					if c.op.RequestBody.Value.Content == nil {
						c.op.RequestBody.Value.Content = make(openapi3.Content)
					}
					c.op.RequestBody.Value.Content[c.mSel.RequestBody.Default] = reqDef
				} else {
					c.op.RequestBody = &openapi3.RequestBodyRef{
						Value: &openapi3.RequestBody{
							Content: openapi3.Content{
								c.mSel.RequestBody.Default: reqDef,
							},
						},
					}
				}
				c.hasChange = true
			} else if len(c.op.RequestBody.Value.Content) == 1 {
				var k string
				for k, reqDef = range c.op.RequestBody.Value.Content {
					c.mSel.RequestBody.Default = k
					break
				}
			} else {
				c.mSel.RequestBody.Default = args.RequestType
				args.InputType = InputType_AsWholeRaw
			}
		} else {
			fmt.Println("request body defined", reqDef)
		}

		if reqDef == nil {
			err = errors.New(errors.ERR_NO_SUCH_SCHEMA, "没有找到需要的传输内容定义，或在多个定义中未指定选择项")
			return
		}

	handle:

		if requestMap == nil && args.RequestBody != nil {
			switch c.mSel.RequestBody.Default {
			case "application/json":
				if bytesBody, ok := args.RequestBody.([]byte); ok {
					json.Unmarshal(bytesBody, &requestMap)
				} else {
					requestMap = make(map[string]interface{}) //TODO
				}
			case "application/x-www-form-urlencoded", "multipart/form-data":
				requestMap = make(map[string]interface{})
				for k, v := range args.RequestBody.(url.Values) {
					requestMap[k] = v[0]
				}
			}
		}

		if args.InputType == InputType_AsStandard {
			var hasChangeSingle bool
			if reqDef != nil && reqDef.Schema != nil && reqDef.Schema.Value != nil {
				for _, schema := range reqDef.Schema.Value.AllOf {
					hasChangeSingle, requestMap, _ = c.generateReqBySchema(&c.handledMark, schema.Value.Properties, reqDef.Encoding, c.mSel, c.op.Parameters, requestMap, fn)
					c.hasChange = hasChangeSingle || c.hasChange
				}
				hasChangeSingle, requestMap, _ = c.generateReqBySchema(&c.handledMark, reqDef.Schema.Value.Properties, reqDef.Encoding, c.mSel, c.op.Parameters, requestMap, fn)
				c.hasChange = hasChangeSingle || c.hasChange
				rawRequest = requestMap
			}
		} else if args.InputType == InputType_AsRawMap {
			rawRequest = args.Input
		} else if args.InputType == InputType_AsWholeRaw {
			rawRequest = args.RequestBody
		}
		// }
		if reqDef != nil {
			//根据定义重新组织请求，再提交
			switch c.mSel.RequestBody.Default {
			case "application/json":
				requestBts, err = json.Marshal(rawRequest)
				c.extraInfos.Request = json.RawMessage(requestBts)
				if err != nil {
					return
				}
			case "application/x-www-form-urlencoded":
				//TODO 这种模式下没有处理使用raw的情况
				var body = make(url.Values)
				for k, v := range requestMap {
					body.Set(k, fmt.Sprintf("%s", v))
				}
				requestBts = []byte(body.Encode())
				c.extraInfos.Request = body.Encode()
			case "multipart/form-data":
				//TODO 这种模式下没有处理使用raw的情况
				var body = &bytes.Buffer{}
				var writer = multipart.NewWriter(body)
				for k, v := range requestMap {
					writer.WriteField(k, fmt.Sprintf("%s", v))
				}
				writer.Close()
				requestBts = body.Bytes()
				ctType = writer.FormDataContentType()
			default:
				c.extraInfos.Request = string(requestBts)
			}
		}
	}
	return
}

func (c *CallingSession) joinSecurityInputs(header http.Header, q url.Values) {

	if c.securitySchemes != nil && c.securityRequirements != nil {
		for _, v := range c.securityRequirements {
			for key := range v {
				sec, ok := c.securitySchemes[key]
				if ok {
					switch sec.Value.Type {
					case "http":
						//TODO
					case "apiKey":
						//TODO
						// input, ok := c.securityInputs[sec.Value.Name]
						// if ok {
						result, err := GetStrValue(
							key,
							sec.Value.In,
							"",
							c, &c.handledMark, nil)
						if err != nil {
							logrus.Error("get security input error", err)
						}
						value := fmt.Sprintf("%v", result)
						switch sec.Value.In {
						case "header":
							header.Set(sec.Value.Name, value)
						case "query":
							q.Set(sec.Value.Name, value)
						case "cookie":
							//TODO
						}
						// }
					case "oauth2":
						//TODO
					case "openIdConnect":
						//TODO
					}
				}
			}
		}
	}
}

func (c *CallingSession) GetResult() *TestResult {
	return c.extraInfos
}

func (c *CallingSession) GetCode() int {
	if c.resp != nil {
		return c.resp.StatusCode
	}
	return 500
}

func (c *CallingSession) HasChange() bool {
	return c.hasChange
}

func (c *CallingSession) GetResponse() *http.Response {
	return c.resp
}

// CallByMethod 根据方法进行调用
func (c *CallingSession) Do(args *CallConfig, fns ...DefHandler) (*CallingSession, error) {

	u, err := c.parseUrl()

	if err != nil {
		return nil, err
	}

	var fn DefHandler

	if len(fns) > 0 {
		fn = fns[0]
	} else {
		fn = &DefaultDefHandler{}
	}

	c.inputs = args

	var q = u.Query()
	// var header = make(http.Header)
	// var reqMethod = args.Selection.
	var req *http.Request

	c.handledMark = make(map[string]interface{})

	requestBts, err := c.generateBody(args, fn)
	if err != nil {
		return nil, err
	}

	logrus.WithField("request body", string(requestBts)).WithField("url", u.String()).Info("request body")

	req, err = http.NewRequest(strings.ToUpper(c.method), u.String(), bytes.NewBuffer(requestBts))
	if err == nil {

		if args.ExtraAuthInfos != nil {
			for _, v := range args.ExtraAuthInfos {
				var sValue = fmt.Sprintf("%v", v.Value)
				switch v.In {
				case "header":
					req.Header.Set(v.Name, sValue)
				case "query":
					q.Set(v.Name, sValue)
				case "cookie":
					//TODO
				}
			}
		}

		//TODO 根据请求类型处理头部信息
		//处理parameters
		for _, par := range c.op.Parameters {
			var value interface{}

			if args.InputType == InputType_AsRawMap || args.InputType == InputType_AsWholeRaw {
				//无论是raw map还是纯raw，都是以这种模式获取参数
				switch par.Value.In {
				case "header":
					if values, ok := args.Header[par.Value.Name]; ok {
						value = values[0]
						goto setValue
					}
				case "query":
					if args.Query.Has(par.Value.Name) {
						value = args.Query.Get(par.Value.Name)
						goto setValue
					}
				case "path":
					if args.PathVars.Has(par.Value.Name) {
						value = args.PathVars.Get(par.Value.Name)
						goto setValue
					}
				case "cookie":
					//TODO
				}

				if args.InputType == InputType_AsWholeRaw {
					if margs, ok := args.Input.(map[string]interface{}); ok {
						if val, ok := margs[par.Value.Name]; ok {
							//从input中获取
							value = val
						}
					}
				} else if par.Value.Schema != nil && par.Value.Schema.Value != nil {
					//设置为default值
					value = par.Value.Schema.Value.Default
				}
			} else {
				//进行计算参数的处理
				// var result string
				value, err = GetStrValue(
					par.Value.Name,
					par.Value.In,
					"",
					c, &c.handledMark, nil)
			}

			// if len(c.mSel.Parameters) > index && c.mSel.Parameters[index].Fixed {
			// 	//设置为固定值时，获取固定值
			// 	value = c.mSel.Parameters[index].Value
			// } else if args.InputType == InputType_AsStandard {
			// 	if val, ok := args.Input[par.Value.Name]; ok {
			// 		//从input中获取
			// 		value = val
			// 		goto setValue
			// 	}
			// } else {

			// }

		setValue:

			switch par.Value.In {
			case "query":
				q.Set(par.Value.Name, EncodingParToString(nil, par.Value.Style, value))
			case "path":
				//替换路径中的参数
				matcher := regexp.MustCompile("{\\s*" + par.Value.Name + "\\s*}")
				u.Path = matcher.ReplaceAllString(u.Path, EncodingParToString(nil, par.Value.Style, value))
			case "header":
				req.Header.Set(par.Value.Name, EncodingParToString(nil, par.Value.Style, value))
			default:
				//TODO 补充其他位置的参数处理
			}
		}

		c.joinSecurityInputs(req.Header, q)

		u.RawQuery = q.Encode()

		// var body = null
		//TODO 处理body信息
		// axiosConfig.body = body
		//设置返回类型
		// if (method.response) {
		//     var response = t.components.responses[method.response.default];
		// }
		c.extraInfos.Url = u.String()

		logrus.WithField("request url", u.String()).Info("request target")

		req.URL = u

		if c.mSel.RequestBody != nil && c.mSel.RequestBody.Default != "" {
			req.Header.Set("Content-Type", c.mSel.RequestBody.Default)
		}

		if args.Origin != "" {
			req.Header.Set("Origin", args.Origin)
		}

		var client *http.Client

		if args.DisableSslCheck {
			tr := &http.Transport{
				TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
			}
			client = &http.Client{Transport: tr}

		} else {
			client = http.DefaultClient
		}

		c.resp, err = client.Do(req)

		if err == nil {

			if args.ShowCertification {
				if c.resp != nil &&
					c.resp.TLS != nil &&
					c.resp.TLS.PeerCertificates != nil &&
					len(c.resp.TLS.PeerCertificates) > 0 {
					c.extraInfos.X509 = c.resp.TLS.PeerCertificates[0]
				}
			}

		}
	}
	return c, nil
}

func (sess *CallingSession) TestResponse(
	fn DefHandler) (bool, error) {

	err := sess.readResponse()
	if err == nil {
		return sess.TestResposeBytes(sess.extraInfos.Response, fn)
	}
	return false, err
}

func (sess *CallingSession) RespHeader() http.Header {
	return sess.resp.Header
}

func (sess *CallingSession) readResponse() error {
	if sess.extraInfos.Response == nil {
		bts, err := io.ReadAll(sess.resp.Body)
		if err == nil {
			sess.extraInfos.Response = bts
		}
		return err
	}
	return nil
}

func (sess *CallingSession) Raw() ([]byte, error) {
	sess.readResponse()
	return sess.extraInfos.Response, nil
}

func (sess *CallingSession) TestResposeBytes(bts []byte, fn DefHandler) (hasChange bool, err error) {
	var rawHeader = sess.RespHeader()

	//get the resposne def
	var resDef *openapi3.ResponseRef
	var ok bool
	var createIfNotDefined = false

	if sess.mSel != nil && sess.mSel.Response != nil && sess.mSel.Response.Default != "" {
		sess.extraInfos.ResponseName = sess.mSel.Response.Default
		resDef = sess.op.Responses.Value(sess.mSel.Response.Default)
		if resDef == nil {
			err = errors.New(errors.ERR_NO_SUCH_METHOD, "没有这个返回"+sess.mSel.Response.Default)
		}
	} else if sess.op.Responses.Len() == 1 {
		for name, res := range sess.op.Responses.Map() {
			sess.extraInfos.ResponseName = name
			resDef = res
			ok = true
		}
	} else {
		//尝试使用状态码匹配对应的输出
		var sCode = strconv.Itoa(sess.resp.StatusCode)
		resDef = sess.op.Responses.Value(sCode)
		if resDef == nil {
			//有多个response，但是没有选择的情况
			resDef = sess.op.Responses.Default()

			if resDef == nil {
				if sess.op.Responses.Len() > 1 {
					err = errors.New(errors.ERR_NO_SUCH_METHOD, "该方法有多个返回，没有指定返回类型")
				} else {
					createIfNotDefined = fn.CreateResponseIfNotDefined() == CreateDefWhenNotDefined
					if createIfNotDefined {
						if sess.op.Responses == nil {
							sess.op.Responses = openapi3.NewResponses()
						}
						resDef = &openapi3.ResponseRef{
							Value: &openapi3.Response{},
						}
						sess.op.Responses.Set("default", resDef)
						ok = true
					} else {
						err = errors.New(errors.ERROR_CALLING_NO_RESPONSE_DEFINED)
					}
				}
			}
			sess.extraInfos.ResponseName = "default"
		} else {
			sess.extraInfos.ResponseName = sCode
		}
	}
	var testOtherRes string
	var tested = make(map[string]bool)
testLoop:
	for ok {
		//TODO check header
		//check body
		var typ string
		if rawHeader != nil {
			typ = strings.Split(rawHeader.Get("Content-Type"), ";")[0] //get rid of the charset
		}
		var searched = typ
		if typ == "text/html" || typ == "" {
			//所有html的结构暂时采用application/json来进行解析
			typ = "application/json"
			sess.extraInfos.Type = "json"
		} else if typ == "text/xml" {
			typ = "application/xml"
			sess.extraInfos.Type = "xml"
		} else if typ == "application/json" {
			sess.extraInfos.Type = "json"
		} else {
			sess.extraInfos.Type = "raw"
		}
		var def *openapi3.MediaType
		if createIfNotDefined {
			var schema = &openapi3.Schema{}
			if len(bts) > 0 {
				if bts[0] == '{' {
					schema.Type = &openapi3.Types{"object"}
				} else if bts[0] == '[' {
					schema.Type = &openapi3.Types{"array"}
				} else if bts[0] == '"' {
					schema.Type = &openapi3.Types{"string"}
				} else if bts[0] == 't' {
					schema.Type = &openapi3.Types{"boolean"}
				} else if bts[0] == 'f' {
					schema.Type = &openapi3.Types{"boolean"}
				} else if bts[0] == 'n' {
					schema.Type = &openapi3.Types{"null"}
				} else if bts[0] == '-' {
					schema.Type = &openapi3.Types{"number"}
				} else if bts[0] >= '0' && bts[0] <= '9' {
					schema.Type = &openapi3.Types{"number"}
				}
			}

			resDef.Value.Content = make(map[string]*openapi3.MediaType)
			def = &openapi3.MediaType{
				Schema: &openapi3.SchemaRef{
					Value: schema,
				},
			}
			resDef.Value.Content[typ] = def
		} else {
			def = resDef.Value.Content.Get(typ)
		}

		if def == nil {
			searched += ";*/*"
			def = resDef.Value.Content.Get("*/*")
		}
		sess.extraInfos.MatchedResponseDef = def
		if def != nil {
			tested[typ] = true
			if typ == "application/json" {
				//尝试对数据进行解码
				var data interface{}
				er := json.Unmarshal(bts, &data)
				if er == nil {
					sess.extraInfos.Response = json.RawMessage(bts)
				}
			}
			if err = def.Validate(context.Background()); err != nil {
				err = errors.New(errors.ERR_VALIDATE_SCHEMA_ERROR, err.Error())
			} else if def.Schema == nil {
				// 没有定义返回类型的情况
			} else if def.Schema.Value == nil {
				err = errors.New(errors.ERR_NO_SUCH_SCHEMA)
				bts, _ := def.Schema.MarshalJSON()
				sess.extraInfos.Def = string(bts)
			} else {
				var res interface{}
				hasChange, res, err = TestSchemaInResponse(typ, def.Schema.Value, bts, fn)
				if errors.Code(err) == errors.ERROR_CALLING_NOT_CORRECT_RESPONSE {
					if sess.op.Responses.Len() > 1 {
						for k, v := range sess.op.Responses.Map() {
							_, done := tested[k]
							if k != sess.mSel.Response.Default && !done {
								resDef = v
								testOtherRes = k
								tested[k] = true
								continue testLoop
							}
						}
					}
				} else if err == nil {
					sess.extraInfos.Parsed = res
				}
			}
		} else {
			err = fmt.Errorf("没有找到合适的返回类型,返回%s，寻找%s,", typ, searched)
		}
		break
	}

	if err == nil {
		if sess.mSel.Response != nil && testOtherRes != sess.mSel.Response.Default {
			err = errors.New(errors.ERROR_CALLING_NOT_CORRECT_RESPONSE, fmt.Sprintf("期望获得%s类型回复，收到%s", sess.mSel.Response.Default, testOtherRes))
		}
	}

	hasChange = hasChange || createIfNotDefined
	return
}

func (c *CallingSession) GetInput(typ, name string) (p *ParameterSelection) {
	p = c.getInput(typ, name)
	if p == nil {
		var types = []string{"path", "query", "header", "cookie", "body"}
		for _, v := range types {
			if v != typ {
				p = c.getInput(v, name)
				if p != nil {
					break
				}
			}
		}
		p = c.getInput("", name)
	}
	return
}

func (c *CallingSession) getInput(typ, name string) (p *ParameterSelection) {
	//尝试从同类型的其他输入中优先获取
	switch typ {
	case "path":
		p = getValueFormUrlValues(c.inputs.PathVars, name)
	case "query":
		p = getValueFormUrlValues(c.inputs.Query, name)
	case "header":
		p = getValueFormUrlValues(c.inputs.Header, name)
	// case "cookie":
	// p= getValueFormUrlValues(c.inputs.Cookies, name)
	case "body":
		if margs, ok := c.inputs.Input.(map[string]interface{}); ok {
			val, ok := margs[name]
			if ok {
				p = &ParameterSelection{
					Value: val,
				}
			}
		}
	}
	if p == nil {
		secI, ok := c.securityInputs[name]
		if ok {
			return &ParameterSelection{
				Value: secI,
			}
		}
	}
	return p
}

func getValueFormUrlValues(values interface {
	Get(key string) string
}, name string) (p *ParameterSelection) {
	if values == nil {
		return
	}
	var v = values.Get(name)
	if v != "" {
		p = &ParameterSelection{
			Value: v,
		}
	}
	return
}

func (c *CallingSession) LoopInType(typ string, fn func(string)) {
	switch typ {
	case "path":
		for k, _ := range c.inputs.PathVars {
			fn(k)
		}
	case "query":
		for k, _ := range c.inputs.Query {
			fn(k)
		}
	case "header":
		for k, _ := range c.inputs.Header {
			fn(k)
		}
	// case "cookie":
	// 	for k, _ := range c.inputs.Cookies {
	// 		fn(k)
	// 	}
	case "body":
		if margs, ok := c.inputs.Input.(map[string]interface{}); ok {
			for k, _ := range margs {
				fn(k)
			}
		}
	}
}

func (c *CallingSession) GetSchema(typ, ct, name string) (schema *openapi3.Schema) {

	switch typ {
	case "path", "query", "header", "cookie":
		for _, def := range c.op.Parameters {
			if def.Value.In == typ && def.Value.Name == name {
				schema = def.Value.Schema.Value
				break
			}
		}
	case "body":
		if c.op.RequestBody != nil {
			schema = c.op.RequestBody.Value.Content.Get(ct).Schema.Value
			if schema != nil && schema.Properties != nil {
				ref := schema.Properties[name]
				if ref != nil {
					schema = ref.Value
				} else {
					schema = nil
				}
			}
		}
	}

	//如果schema为空，则从tempSecutirySchemas中获取
	if schema == nil && c.securitySchemes != nil {
		ref := c.securitySchemes[name]
		if ref != nil {
			logrus.WithField("name", name).Error("找到security schema")
			schema = &openapi3.Schema{
				Extensions: ref.Value.Extensions,
				Default:    "",
			}
		}
	}

	return schema

}
